#####################################################################
# Product:      KnowledgeEngine (Plone Edition)
#
# File:         Link.py
#
# Copyright:    Copyright (c) 2004, Faulkner Technologies
#
# Author:       Tom Cameron, Brian Oliver
#
# Description:  A Link is a primitive Component
#               to represent simple multiple choice questions of 
#               mutually exclusive answers.
#
# See Also:     Component, Model
#####################################################################

#####################################################################
## Knowledge Engine Library Imports
from Products.KnowledgeEngine.Common import *
from Products.KnowledgeEngine.Core import *
from Products.KnowledgeEngine.Component import Component, ComponentInfo
from Products.KnowledgeEngine.Assessible import Assessible
from Products.KnowledgeEngine.AssessmentFilter import AssessmentFilter
from Products.KnowledgeEngine.AssessmentComponentState import AssessmentComponentState
from Products.KnowledgeEngine.AssessmentComponentValueTypes import *
from Products.KnowledgeEngine.Exportable import Exportable
from Products.KnowledgeEngine.KERLExtensions import KERLExtensions
from Products.KnowledgeEngine.KERLProcessor import *
from Products.KnowledgeEngine.Scorable import *
from Products.KnowledgeEngine.PropertyDefinition import *
from Products.KnowledgeEngine.PropertyTypes import *
from Products.KnowledgeEngine.PropertyFormats import *
from Products.KnowledgeEngine.PropertyStructures import *
from Products.KnowledgeEngine.KERFProcessor import *
from Products.KnowledgeEngine.Graphing.Graphable import *
from Products.KnowledgeEngine.Graphing.Datum import *

from Products.KnowledgeEngine.Utils.DisplayList import DisplayList
from Products.KnowledgeEngine.Utils.CallbackIterator import CallbackIterator
from Products.KnowledgeEngine.Model import Model

from Products.KnowledgeEngine.KERLProcessor import InvalidKerlOperator

#####################################################################
## Zope CMF Library Imports
from Products.CMFCore.PortalContent import PortalContent
from Products.CMFCore.ActionInformation import ActionInformation
from Products.CMFCore import permissions
from Products.CMFCore.permissions import ModifyPortalContent

#####################################################################
## Zope Library Imports
from Globals import DTMLFile
from App.ImageFile import ImageFile
from Globals import InitializeClass
from AccessControl import ClassSecurityInfo
from AccessControl.Role import RoleManager

#####################################################################
## Python Library Imports
import string
import re
import types


class LinkException(Exception): pass



#####################################################################
## LinkInfo Class Definition
class LinkInfo(ComponentInfo, ScorableInfo):
    """
        Defines the class info methods, defining information about
        the class without needing an instance of the class
    """

    #---------------------------------------------------------------#
    
    def getClassName(self):
        """
            Return the class name
        """
        
        return 'Link'

    #---------------------------------------------------------------#

    def getDisplayName(self):
        """
            Return a friendly name for the class instances
            (this is used for displaying in lists etc)
        """
    
        return "Link Question"
    
    #---------------------------------------------------------------#

    def getDescription(self):
        """
            Return a friendly description on the purpose
            of this class.  This description is often used in the 
            Property Editor for instances of this class
        """
    
        return "The Link Component create a one-many link between models"
        
    #---------------------------------------------------------------#

    def getIdentityType(self):
        """
            Return the identity type.  
        """
        
        return "link"
        
    #---------------------------------------------------------------#
    
    def getClassIcon(self):
        """
            Return the appropriate icon file for the class.
        """
        
        return ImageFile('skins/Link_icon.gif', globals())

    #---------------------------------------------------------------#

    def isInstanceOf(self, className):
        """
            Returns if the class is an instance of the specified
            className, or implements the interface specified 
            by the className
        """

        return className in ("Link", "Linkable", "Assessible", 'Exportable')\
               or ScorableInfo.isInstanceOf(self,className)\
               or ComponentInfo.isInstanceOf(self, className)
    
    #---------------------------------------------------------------#

    def onConfigureProperties(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register 
            class specific properties using the PropertyReflection interface.
            
            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered
            
            NOTE: This method is called before ClassInfo.onAfterStartup()            
        """

        #initialise the super-class properties for reflection
        ComponentInfo.onConfigureProperties(self)
        ScorableInfo.onConfigureProperties(self)
        
        #change the display name for 'content' to be 'Question'
        self.getPropertyDefinition("content").setDisplayName("Question")
        
        #change the description
        self.getPropertyDefinition("content").setDescription("The question to be asked (only appears when performing assessment)")


        
        def getModels(component):
            models = [brain.getObject() for brain in component.knowledgeengine_genericobjectcatalogservice(getClassName='Model')]
            return models
        
        self.addPropertyDefinition(PropertyDefinition(
            identity='linkModel',
            height="auto",
            singleSelection=true,
            displayName="Model",
            description="Select the model to link to",
            defaultValue="",
            propertyType="L",
            getModelFunction=getModels,
            isDisplayableFunction=lambda component: component.isInstanceOf('Model'),
            propertyFormat=pf_GENERIC_OBJECT_CHOOSER,
            propertyStructure=ps_ATOMIC
        ))
        
        def getChosenModel(component):
            
            linkModel = getattr(component,'linkModel',[])
            if not linkModel: return None
            modelIdentity = linkModel[0]
            
            if modelIdentity:
                brains = component.knowledgeengine_genericobjectcatalogservice(getIdentity=modelIdentity)
                if brains: return brains[0].getObject()
                    
        
        def isDisplayable(x):            
            return x.isInstanceOf('Assessible') or x.isInstanceOf('Model') or x.isInstanceOf('Section') or x.isInstanceOf('Selection') or x.isInstanceOf("Repository")

        def isSelectable(x):            
            #return True
            return x.isInstanceOf("Repository") or not x.isContainer()        
        
        def nodeTitleFunction(genericObject):
            title    = genericObject.title_or_id()
            identity = genericObject.getIdentity()
            
            if not genericObject.isInstanceOf('Assessible'):
                return genericObject.title_or_id()
            
            if hasattr(genericObject, 'getSearchIndex'):
                searchIndex = genericObject.getSearchIndex()
                if searchIndex:
                    return "%s idx=%s (%s)" % ( title, searchIndex, identity)
            
            return "%s (%s)" % ( genericObject.title_or_id(), genericObject.getIdentity())
        
        self.addPropertyDefinition(PropertyDefinition(
            identity='vocabSource',
            displayName="Components",
            description="Select the components to link to",
            defaultValue="",
            propertyType="L",
            getModelFunction=getChosenModel,
            isDisplayableFunction=isDisplayable,
            isSelectableFunction=isSelectable,
            isInitiallyExpandedFunction=lambda x:False,
            nodeTitleFunction=nodeTitleFunction,
            #onClickEvent="document.getElementsByName('kedisplayFormat')[0].value += '<$' + this.value + '$>'",
            # singleSelection=true,
            propertyFormat=pf_GENERIC_OBJECT_CHOOSER,
            propertyStructure=ps_ATOMIC
        ))        
        
        
        self.addPropertyDefinition(PropertyDefinition(
            identity="displayFormat",
            displayName="Display Format",
            description="KERF Expression, use component identity or search indexes to format entries",
            defaultValue="",
            propertyFormat=pf_TEXT_SINGLELINE,
            
        ))
        
        self.addPropertyDefinition(PropertyDefinition(
            identity="displayMode", 
            displayName="Display Mode", 
            description="Select Popup search for larger vocabularies", 
            defaultValue="DROPDOWN", 
            propertyType=pt_STRING, 
            propertyFormat=pf_NATIVE, 
            propertyStructure=ps_ATOMIC, 
            hasEnumeratedValues=true, 
            enumeratedValuesMapping=[('Dropdown List','DROPDOWN'),('Popup Search','POPUP')]
        ))
        
        self.addPropertyDefinition(PropertyDefinition(
            identity="searchFunction",
            displayName="Search Function",
            description="(Optional) A custom search function that returns [(assessmentIdentity, display),...]",
            propertyFormat=pf_TEXT_SINGLELINE,
            defaultValue=""
        ))            
      
            
        

    #---------------------------------------------------------------#

#####################################################################
## Link Class Definition
class Link(Component, Assessible, KERLExtensions, Exportable):
    """
        Defines a simple component to represent one or more lines of 
        text entered during an assessment.
    """

    #---------------------------------------------------------------#

    #TRANSIENT: a ClassInfo implementation instance to provide info about this class
    _CLASSINFO = LinkInfo()
    
    #---------------------------------------------------------------#

    # DTML Methods
    index_html = DTMLFile('dtml/index_html', globals())
    simplereport_html = DTMLFile('dtml/simplereport_html', globals())        
    scoredreport_html = DTMLFile('dtml/scoredreport_html', globals())
    scored360report_html = DTMLFile('dtml/scored360report_html', globals())
    groupedreport_html = DTMLFile('dtml/groupedreport_html', globals())    

    #---------------------------------------------------------------#

    def __init__(self, id, identity):
        "Constructor for the Component"        

        Component.__init__(self, id, identity)

    #---------------------------------------------------------------#

    def onAfterInstanciation(self):
        """
            Handle Knowledge Engine specific actions after this object 
            has been loaded into memory.  
            
            This is useful for performing backwards compatibility
        """
        
        #perform Component.onAfterInstanciation processing
        Component.onAfterInstanciation(self)

       
        ###print "DEBUG: Link.onAfterInstanciation: Completed"

    #---------------------------------------------------------------#

    def onAfterIdentityChange(self, identityMappings):
        """
            Handle Knowledge Engine specific actions after the 
            identity of this object has changed.
            
            The identityMappings dictionary holds 
            mappings from oldIdentities -> newIdentities
            (including this object)
                        
        """

        #perform default identity changes
        Component.onAfterIdentityChange(self, identityMappings)
          
        #the answerlist has changed
        self._p_changed = 1
                
    #---------------------------------------------------------------#
    
    def getLinkModelIdentity(self):
        """ 
            Return the identity of the linked model
        """
        
        return self.linkModel
    
    #---------------------------------------------------------------#
    
    def setLinkModelIdentity(self, value):
        """ Sets the linked model identity """
        
        self.linkModel = value
    
    #---------------------------------------------------------------#
    
    def getLinkModel(self):
        """
            Returns the linked model
        """
        
        return self.getComponentByIdentity( self.getLinkModelIdentity() )
    

    #---------------------------------------------------------------#

    def getAnswerByAid( self, assessmentIdentity ):
        """ 
            Returns the display value for the specified answer
        """
        
        #assessment = self.knowledgeengine_assessmentstorageservice.loadAssessment( assessmentIdentity )

        

        #state = self.knowledgeengine_assessmentstorageservice.loadComponentState( assessment, self.getIdentity() )
        
        
       # print "State is: %s" % `state`
        
        answers = self.getAnswerList( assessmentIdentities=[assessmentIdentity], currentState=assessmentIdentity)
        if answers:
            return answers[0]


    #---------------------------------------------------------------#
    
    def getSearchFunction(self):
        """
            Returns the custom search function to use
        """
        
        return getattr(self, 'searchFunction', None)

    #---------------------------------------------------------------#
    
    def setSearchFunction(self, searchFunction):
        """
            Sets the custom search function
        """
        
        self.searchFunction = searchFunction
    
    
    #---------------------------------------------------------------#    
   
    def getVocab(self):
        """ Returns a standard Vocab (List of two-tuple key value pairs) of this components data """
        
        return [ (key, value[0]) for (key, value) in self.getAnswerList()]

    #---------------------------------------------------------------#
        
    def getVocabSource(self):
        """ Returns the raw vocab source """
        
        return getattr(self.aq_explicit, 'vocabSource', [])
                
    #---------------------------------------------------------------#
    
    def setVocabSource(self, value):
        
        self.vocabSource = value
        
    #---------------------------------------------------------------#    

    def getAnswerList(self, assessmentIdentities=None, search=None, currentState=None):
        """
            Returns a display list (List of key=>(col1,col2)) tuples),
            Optionally filtering on the substring `search` or a given list of assessmentIdentities
        """
        
        # Iterate over the list of chosen components and find all the repositories.
        # We filter on assessment only in these.
        repos = []
        componentIdentities = self.vocabSource
        for componentIdentity in componentIdentities:
            component = self.getComponentByIdentity( componentIdentity )
            if component.isInstanceOf("Repository"):
                repos.append( componentIdentity )
            
        
        
        # If there's a custom search function, use it
        searchFunction = self.getSearchFunction()
        if searchFunction:
            
            # Look in the current context for the specified method (Generally we would be trying to find it in skins)
            func = getattr(self, searchFunction, None)
            
            
        
            if func:
                                                                                                                        
                # Look for the 'currentState' 
                if currentState and hasattr( func, 'params' ):
                                                            
                    params = func.params()
                    if 'currentState' in params: 
                        return func(search=search, assessmentIdentities=assessmentIdentities, currentState=currentState)
                                          
                return func(search=search, assessmentIdentities=assessmentIdentities)
            else:
                raise LinkException("Could not find searchFunction '%s'" % searchFunction)
        
        
        # ... If not, use the default query method
        
        storage = self.getModel().getAssessmentStorageService()
                  
        answers = storage.getComponentAnswers( self.vocabSource, assessmentIdentities=assessmentIdentities, search=search, repositoryIdentities=repos )
        
        format = self.displayFormat
        
        # Really, really bad hack- were are executing KERF the wrong way, and need to fsck with it a little first.
        # FIXME!
        
        ##HACKHACK - Remove KERF start and end tags
        format = format.replace('<kerf>','')
        format = format.replace('</kerf>','')
        
        ##HACKHACK - Support existing component:value syntax
        format = format.replace(':value','')
        format = format.replace(':value','')
        
        def render(row):
            """ Callback method to render each for for the dropdown list of answers """
            aid, data = row
            try:
                resolved = resolveKERFProperties( format, assessment=None, component=data )
                if resolved:
                    formatted = eval(resolved)
                else:
                    formatted = "(%s)" % aid
                                        
                return aid, (formatted,)
            except KeyError: # Something went wrong resolving the KERF
                return aid, (format,)
            
        return CallbackIterator( list(answers), render )
           
                
    #---------------------------------------------------------------#
        
    def getAnswer(self, answerID):
        """
            Return the answer with the specified id or None if
            not found
        """
        
        for key, value in self.getAnswerList():        
            if key == answerID:
                return key, value     
        return None

    #---------------------------------------------------------------#
        
        
    #---------------------------------------------------------------#
        
    def getAnswerReportContent(self, assessment):
        """
            Evalutes the answer report content to display

            RETURN TYPE: string        
        """
        
        #get the identity
        identity = self.getIdentity()

        #get the stored state
        id = assessment.getComponentState(identity).getValue()
        
        #get the answer
        key, answer = self.getAnswer(id)

        return answer
        
    #---------------------------------------------------------------#

    def getComponentProperty(self, identity, propertyName, assessment = None):
        """
            Returns the value of the specified property, given the
            identity of the component (which may be used to lookup states) and
            an assessment.

            RETURNS: None if the property doesn't exist
        """

        print "LINK: getComponentProperty( %s , %s, %s) " % (identity, propertyName, assessment.getIdentity())

        if propertyName == "value":
            return assessment.getComponentState(identity).getValue()
        else:
            return Component.getComponentProperty(self, identity, propertyName, assessment)
        
        
    #---------------------------------------------------------------#
    # BEGIN: Assessible Implementation                              #
    #---------------------------------------------------------------#
        
    def getAssessmentValueType(self):
        """
            ABSTRACT: Returns the type of the value that is to be stored
            for this component when it's assessment state is persisted.
            
            NOTE 1: See AssessmentComponentValueTypes for a list of defined contants       
            NOTE 2: This method MUST be overriden in the implementing
            class to specify the correct type for value persistence
            
            RETURN TYPE: String
        """
        
        #multiplechoice assessment values are strings
        return st_STRING
        
    #---------------------------------------------------------------#
        
    def getDefaultAssessmentState(self, assessment):
        """
            ABSTRACT: Constructs and returns a suitable 'default' 
            AssessmentComponentState for the provided Assessment
            instance.

            NOTE: This method MUST be overriden in the implementing
            class to provide a suitable default value for the component
            
            RETURN TYPE: AssessmentComponentState
        """
        
        return AssessmentComponentState(assessment.getIdentity(), assessment.getModelIdentity(), self.getIdentity(), self.getAssessmentValueType(), "", revision=assessment.getRevision())
            
    #---------------------------------------------------------------#

    def updateAssessmentState(self, assessment, formValue):
        """
            ABSTRACT: Updates the specified assessment using
            the provided HTTP REQUEST formValue.
            
            NOTE: This method MUST be overriden in the implementing
            class to suitably update the component state
        """
            
        Component.updateAssessmentState( self, assessment, formValue )              
            
        #get the current assessment state for the component from the assessment
        state = assessment.getComponentState(self.getIdentity())
        
        #update the current state
        state.setValue(formValue)
        
        #update the assessment
        assessment.setIsModified(true)
        
        searchIndex = getattr(self, 'searchIndex', False)
        if searchIndex:
            state.setSearchIndex(searchIndex)
            
            chosen = self.getAnswer(formValue)
            if chosen:
                state.setSearchValue(chosen[0])
            else:
                state.setSearchValue(None)                
        
            
    #---------------------------------------------------------------#
    # END: Assessible Implementation                                #
    #---------------------------------------------------------------#


    #---------------------------------------------------------------#
    # BEGIN: Exportable Implementation                              #
    #---------------------------------------------------------------#
    def getComponentCSVValue(self, assessment, displayReportContent=False):
        """
            Return the CSV friendly version of the component.
        """
        
        (assessmentIdentity, answer) = self.getAnswerByAid(self.getComponentValue(assessment))
        
        return ",".join(answer)
    #---------------------------------------------------------------#
    # END: Exportable Implementation                                #
    #---------------------------------------------------------------#



    #---------------------------------------------------------------#
    # BEGIN: KERLExtensions Implementation                          #
    #---------------------------------------------------------------#

    def compileKERLtoJavaScript(self, expression):
        """
            Returns a Python string that represents a JavaScript
            expression, which may be used to evaluate the status
            of the component class specific KERL expression.

            NOTE: this method is primarily used to produce
            JavaScript expressions that form the basis of the
            client-side showif technology.
            
            RETURN TYPE: String         
        """
        
        
        #example: cond="['mcq1','selected', 'id']" returns 'document.getElementById("mcq1").value==value'

        #get the identity (which is the identity of itself)        
        identity = self.getIdentity()

        #determine the operator we are going to process
        opr = expression[1]
        #-------------------------------
        #an answer is 'selected' if the actual selected answer is the specified answer
        if opr == "selected":

            return "document.getElementById(\"%s\").value == \"%s\"" % (identity, asJavaScript(expression[2]))
            
        #-------------------------------
        #an answer is 'unselected' if the actual selected answer isn't the specified answer
        elif opr == "unselected":

            return "document.getElementById(\"%s\").value != \"%s\"" % (identity, asJavaScript(expression[2]))
            
        #-------------------------------
        #a question is correct if the selected answer (there must be one) is correct
        elif opr == "correct":

            #find the correct answer
            for answer in self.getAnswerList():
                
                #remember the correct answer
                if answer.isCorrect():
                    correctAnswer = answer

            #now ensure that the selected answer is the correct answer
            return "document.getElementById(\"%s\").value == \"%s\"" % (identity, asJavaScript(correctAnswer.getAnswerID()))
            
           
        #-------------------------------
        #a question is incorrect if the selected answer isn't the correct one
        elif opr == "incorrect":

            #find the correct answer
            for answer in self.getAnswerList():
                
                #remember the correct answer
                if answer.isCorrect():
                    correctAnswer = answer

            #ensure that the selected answer is not the correct answer
            return "document.getElementById(\"%s\").value != \"%s\"" % (identity, asJavaScript(correctAnswer.getAnswerID()))
        
        #-------------------------------
        #a question has been 'attempted' iff one of the answers are selected
        elif opr == "attempted":
        
            javascript = ""
            for answer in self.getAnswerList():
            
                #add an || or operator between expressions
                if javascript != "":
                    javascript += " || "
                
                #add the condition for the current answer
                javascript += "document.getElementById(\"%s\").value == \"%s\"" % (identity, asJavaScript(answer.getAnswerID()))
                
            return "(" + javascript + ")"
        
        #-------------------------------
        #a question is 'notattempted' iff none of the answers are selected
        elif opr == "notattempted":

            javascript = ""
            for answer in self.getAnswerList():
            
                #add an && and operator between expressions
                if javascript != "":
                    javascript += " && "
                
                #add the condition for the current answer
                javascript += "document.getElementById(\"%s\").value != \"%s\"" % (identity, asJavaScript(answer[0]))
                
            return "(" + javascript + ")"
        
        #-------------------------------
        else:
            raise InvalidKerlOperator, opr
            #return "true"

    #---------------------------------------------------------------#

    def compileKERLtoEnglish(self, expression):
        """
            Returns a string representing an 'English' version
            of the component class specific KERL expression.

            NOTE: this method is primarily used to produce
            English descriptions of KERL expressions that may be 
            used to describe things like AssessmentFilters
            
            RETURN TYPE: String         
        """
        
        #determine the operator we are going to process
        operator = expression[1]
        
        #-------------------------------
        #an answer is 'selected' if the actual selected answer is the specified answer
        if operator == "selected":

            return "%s '%s' answer %s is selected" % (self.getClassInfo().getDisplayName(), self.getTitleOrId(), self.getAnswer(expression[2]).getOption())
            
        #-------------------------------
        #an answer is 'unselected' if the actual selected answer isn't the specified answer
        elif operator == "unselected":

            return "%s '%s' answer %s is not selected" % (self.getClassInfo().getDisplayName(), self.getTitleOrId(), self.getAnswer(expression[2]).getOption())
            
        #-------------------------------
        #a question is correct if the selected answer (there must be one) is correct
        elif operator == "correct":

            return "%s '%s' correctis answer selected" % (self.getClassInfo().getDisplayName(), self.getTitleOrId())
            
           
        #-------------------------------
        #a question is incorrect if the selected answer isn't the correct one
        elif operator == "incorrect":

            return "%s '%s' incorrect is answer selected" % (self.getClassInfo().getDisplayName(), self.getTitleOrId())
        
        #-------------------------------
        #a question has been 'attempted' iff one of the answers are selected
        elif operator == "attempted":
        
            return "%s '%s' is attempted" % (self.getClassInfo().getDisplayName(), self.getTitleOrId())
        
        #-------------------------------
        #a question is 'notattempted' iff none of the answers are selected
        elif operator == "notattempted":

            return "%s '%s' is not attempted" % (self.getClassInfo().getDisplayName(), self.getTitleOrId())
        
        #-------------------------------
        else:
            raise InvalidKerlOperator, opr
            #return ""

    #---------------------------------------------------------------#

    def evaluate(self, expression, assessment):
        """
            Evalutes the component class specific KERL expression
            using the provided assessment.

            NOTE: this method is primarily used to produce
            JavaScript expressions that form the basis of the
            client-side showif technology.

            RETURN TYPE: Boolean         
        """
        
        #get the identity (which is the identity of itself)        
        identity = self.getIdentity()

        #get the stored state
        state = assessment.getComponentState(identity)

        #determine the operator we are going to process
        opr = expression[1]
        #-------------------------------
        #an answer is 'selected' if the actual selected answer is the specified answer
        if opr == "selected":

            return state.getValue() == expression[2]
            
        #-------------------------------
        #an answer is 'unselected' if the actual selected answer isn't the specified answer
        elif opr == "unselected":

            return state.getValue() != expression[2]
            
        #-------------------------------
        #a question is correct if the selected answer (there must be one) is correct
        elif opr == "correct":

            #find the correct answer
            for answer in self.getAnswerList():
                
                #remember the correct answer
                if answer.isCorrect():
                    correctAnswer = answer

            #now ensure that the selected answer is the correct answer
            return state.getValue() == correctAnswer.getAnswerID()
            
           
        #-------------------------------
        #a question is incorrect if the selected answer isn't the correct one
        elif opr == "incorrect":

            #find the correct answer
            for answer in self.getAnswerList():
                
                #remember the correct answer
                if answer.isCorrect():
                    correctAnswer = answer

            #ensure that the selected answer is not the correct answer
            return state.getValue() != correctAnswer.getAnswerID()
        
        #-------------------------------
        #a question has been 'attempted' iff one of the answers are selected
        elif opr == "attempted":
        
            attempted = false
            for answer in self.getAnswerList():            
                attempted = attempted or (state.getValue() == answer.getAnswerID())
                
            return attempted
        
        #-------------------------------
        #a question is 'notattempted' iff none of the answers are selected
        elif opr == "notattempted":

            notattempted = true
            for answer in self.getAnswerList():            
                notattempted = notattempted and (state.getValue() != answer.getAnswerID())
                
            return notattempted
        
        #-------------------------------
        else:
            raise InvalidKerlOperator, opr
            #return true
    
    #---------------------------------------------------------------#

    def getDependencies(self, expression):
        """
            Returns a list of components that are required to
            evaluate the component class specific KERL expression.

            NOTE: Includes the specific component itself.

            RETURN TYPE: {Component} indexed by Component.getIdentity()
       """

        return {self.getIdentity():self}
    
    #---------------------------------------------------------------#

    def updateKERLIdentities(self, expression, identityMappings):
        """
            Returns the expression with the appropriate old identities
            (as identified by the identityMappings) replaced with 
            new identities.
        """
        
        #get the component identity from the expression (first element)
        identity = expression[0]
        
        #is the component in the identityMappings?
        if identityMappings.has_key(identity):
            expression[0] = identityMappings[identity]
        
        return expression

    #---------------------------------------------------------------#
    # END: KERLExtensions Implementation                            #
    #---------------------------------------------------------------#

    def generateGraphableStatistics(self, baseAssessmentFilter = None, user = None, nrAssessments = None):
        """
            Generates a Graphable instance containing statistic
            information for this component - that may be graphed.
            
            The provided assessmentFilter is used as a basis for
            generating statistics information.  Setting this to
            None (the default) means all assessments are used.
            
            RETURN TYPE: Graphable
        """
        
        #ensure the baseAssessmentFilter is valid (not None)
        if baseAssessmentFilter is None:
            baseAssessmentFilter = self.getModel().getAllAssessmentsFilter()
        
        #we need to use the assessment storage service to get the statistics
        assessmentStorageService = self.getModel().getAssessmentStorageService()

        #determine the number of assessments (if necessary)
        if nrAssessments is None:
            nrAssessments = assessmentStorageService.getNrAssessmentsSatisfyingFilter(baseAssessmentFilter, user)   
        
        #we need a Graphable for the results
        graphable = Graphable(self.getTitleOrId(), "The following chart outlines the selection statistics for the '%s' component in the %d current assessments." % (self.getTitleOrId(), nrAssessments), 0, nrAssessments)
        
        #create a datum for the number of times each answer has been selected in the assessments
        for answer in self.getAnswerList():

            #create a filter for the answer      
            filterTitle = "# Answer %s)." % answer.getOption()
            filterExpression = parseKERL("['%s', 'selected', '%s']" % (self.getIdentity(), answer.getAnswerID()))
            assessmentFilter = AssessmentFilter(title=filterTitle, expression=simplifyKERL(['all', baseAssessmentFilter.getExpression(), filterExpression]), model=self.getModel(), assessmentScope=baseAssessmentFilter.getAssessmentScope())
            
            #determine the color for the datum
            if self.getShowCorrectness():
                if answer.isCorrect():
                    datumColor = "green"
                    datumCallout = "Correct"
                else:
                    datumColor = "red"
                    datumCallout = "Incorrect"
            else:
                datumColor = "green"
                datumCallout = None

            #filter the assessments
            nrSelectedAssessments = assessmentStorageService.getNrAssessmentsSatisfyingFilter(assessmentFilter, user)
            datum = Datum(identity=answer.getAnswerID(), title="# Answer %s)." % answer.getOption(), value=nrSelectedAssessments, color=datumColor, footnote=answer.getContent(), callout = datumCallout, context={"ASSESSMENT_FILTER":assessmentFilter})
            graphable.addDatum(datum)
            
            #remove the actual selections to work out the undefined count
            nrAssessments = nrAssessments - nrSelectedAssessments

        #create a datum for the number of times no choices have been made in the assessments
        datum = Datum(identity="undefined", title="# Undefined.", value=nrAssessments, color="orange", footnote="As it is possible for an end-user to avoid selecting an answer, some assessments may have 'undefined' answers.", callout = "Incorrect")        
        graphable.addDatum(datum)
        
        return graphable

    #---------------------------------------------------------------#

    def listActions(self, info=None):
        """
            PLONE Support:

            Returns a tuple of ActionInformation instances
            specifying the available CMF Actions for the object

            overrides:

            (CMF Actions are different from Knowledge Engine Actions)
        """

        #add the super-class actions first
        lCMFActions = Component.listActions(self, info)

        #add the 'statistics' action
        lCMFActions = lCMFActions + (
                        ActionInformation(
                            id='statistics',
                            title='statistics',
                            description='Current Statistics',
                            category='object',
                            condition='',
                            permissions=(permissions.ListFolderContents,),
                            priority=1,
                            visible=1,
                            action='string:${object_url}/Link_Statistics'
                        ),)
                        
        return lCMFActions

    #---------------------------------------------------------------#

    def getIsPopup(self):
        """ 
            Returns TRUE if this Link component should use a popup search
            instead of a dropdown list
        """
        return self.displayMode == "POPUP"

    #---------------------------------------------------------------#


#####################################################################
## Class Initialisation

# Register Component Information with the Knowledge Engine
registerGenericObjectClass(Link)

#InitializeClass(Answer)
#####################################################################